﻿/**
 * ...
 * @author aboutwhat
 */


package format.hxml;

import haxe.io.Eof;
import haxe.io.Input;
import format.hxml.Data.Option;
import format.hxml.Data.Remap;
import format.hxml.Data.Library;
import format.hxml.Data.Resource;
import format.hxml.Data.SwfHeader;

using StringTools;
using Lambda;

typedef CS = format.hxml.Constants;

class Reader 
{
    static var flagParser = ~/^\-{1,2}([a-z\-A-Z]*)$/;
    static var optParser = ~/^\-{1,2}([a-z\-A-Z]*) +([^ ].+)$/;
    static var remapParser = ~/^([a-z][a-z0-9A-Z]*):([a-z][a-z0-9A-Z]*)$/;
    static var libraryParser = ~/^([^ :]+)(:([^ :]+))?$/;
    static var resourceParer = ~/^([^ :]+)@([a-zA-Z0-9_]+)$/;
    static var swfHeaderParser = ~/^([0-9]+):([0-9]+):([0-9]+):([0-9A-Fa-f]+)$/;
    
    var input : Input;
    
    public function new (i : Input) 
    { 
        input = i; 
    }
    
    public function read ():Data
    {
        var res : Data = new Data();

        while (true) {
            try {
                var line = input.readLine();
                var lineCopy = line;
                
                line = line.trim();
                // ignore empty lines
                try {
                    if (line != "") {
                        res.add(readLine(line));
                    
                    }
                } catch (e : String) {
                    throw "Error (" + e + ") occured, while parsing\nline:\n(" + lineCopy + ")\ntrimmed line:\n(" + line + ")";
                }
            } catch (e : Eof) {
                break;
            }
        }
        return res;
    }
    
    function readLine(line:String)
    {
        if (flagParser.match(line)) 
        {
            return optionToEnum(flagParser.matched(1));
            
        } 
        else if (optParser.match(line)) 
        {
            return optionToEnum(optParser.matched(1), optParser.matched(2).trim());
        }
        throw "Invalid Line: (" + line + ")";
    }
    
    function optionToEnum (opt : String, ?value : String) 
    {
        return switch (opt) 
        {
            case CS.cp :            Option.cp(value);
            case CS.js :            Option.js(value);
            case CS.swf :           Option.swf(value);
            case CS.swf9 :          Option.swf9(value);
            case CS.as3 :           Option.as3(value);
            case CS.neko :          Option.neko(value);
            case CS.php :           Option.php(value);
            case CS.cpp :           Option.cpp(value);
            case CS.xml :           Option.xml(value);
            case CS.main :          Option.main(value);
            case CS.lib :           Option.lib(strToLibrary(value));
            case CS.D :             Option.D(value);
            case CS.v :             Option.v;
            case CS.debug :         Option.debug;
            case CS.swfVersion :    Option.swfVersion(Std.parseInt(value));
            case CS.swfHeader :     Option.swfHeader(strToSwfHeader(value));
            case CS.swfLib :        Option.swfLib(value);
            case CS.x :             Option.x(value);
            case CS.resource :      Option.resource(strToResource(value));
            case CS.exclude :       Option.exclude(value);
            case CS.prompt :        Option.prompt;
            case CS.cmd :           Option.cmd(value);
            case CS.flashStrict :   Option.flashStrict;
            case CS.noTraces :      Option.noTraces;
            case CS.flashUseStage : Option.flashUseStage;
            case CS.nekoSource :    Option.nekoSource;
            case CS.genHxHeaders :  Option.genHxHeaders;
            case CS.display :       Option.display;
            case CS.noOutput :      Option.noOutput;
            case CS.times :         Option.times;
            case CS.noInline :      Option.noInline;
            case CS.noOpt :         Option.noOpt;
            case CS.phpFront :      Option.phpFront(value);
            case CS.jsNamespace :   Option.jsNamespace(value);
            case CS.remap :         Option.remap(strToRemap(value));
            case CS.help :          Option.help;
            case CS.next :          Option.next;
        }
    }
    
    function strToLibrary (str : String) : Library 
    {
        var m = libraryParser;
        
        if (!m.match(str)) 
            throw "Invalid Library Format, must be lib:version";
        
        return 
        {
            library : m.matched(1),
            version : m.matched(3)
        }
    }
    
    function strToSwfHeader (str : String) : SwfHeader 
    {
        var m = swfHeaderParser;

        if (!m.match(str)) 
            throw "Invalid Value for swf-header Option (" + str + "), expected: width:height:fps:color";
        
        return 
        {
            width : Std.parseInt(m.matched(1)),
            height : Std.parseInt(m.matched(2)),
            fps : Std.parseInt(m.matched(3)),
            color : Std.parseInt("0x" + m.matched(4))
        }
    }
    
    function strToRemap (str : String) : Remap 
    {
        var m = remapParser;
        
        if (!m.match(str)) 
            throw "Invalid Value for Remap Option: " + str + ", expected: package:target";
        
        return 
        {
            source : m.matched(1),
            target : m.matched(2)
        }
    }
    
    function strToResource (str : String) : Resource
    {
        var m = resourceParer;

        if (!m.match(str)) 
            throw "Invalid Value for Remap Option: " + str + ", expected: package:target";
        
        return 
        {
            file : m.matched(1),
            name : m.matched(2)
        }
    }
}